package com.peak.spring.boot.core.mapper;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.RandomStringGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.peak.spring.boot.api.constant.ErrorMessage;
import com.peak.spring.boot.api.entity.Identity;
import com.peak.spring.boot.core.exception.Exceptions;
import com.peak.spring.boot.core.exception.ProjectException;
import com.peak.spring.boot.tools.Converts;
import com.peak.spring.boot.tools.DatePattern;
import com.peak.spring.boot.tools.Dates;
import com.peak.spring.boot.tools.Emptys;
import com.peak.spring.boot.tools.PropertiesLoader;
import com.peak.spring.boot.tools.Reflections;

public class BeanMapper {
	
	private static final Logger log = LoggerFactory.getLogger(BeanMapper.class);

	private static PropertiesLoader loader = null;

	static {
		loader = new PropertiesLoader("classpath:format.properties");
	}

	/**
	 * 转换集合类型，转成Map，便于发送到前台
	 * 
	 * @param sources
	 * @param targetClass
	 * @return
	 */
	public static List<Map<String, Object>> mapList(List<?> sources, Class<?> targetClass) {
		List<Map<String, Object>> result = Lists.newArrayList();
		if (Emptys.isNotEmpty(sources)) {
			result = sources.stream().map(p -> map(p, targetClass)).collect(Collectors.toList());
		}
		return result;
	}

	/**
	 * 转换对象为Map
	 * 
	 * @param source
	 * @param targetClass
	 * @return
	 */
	public static <T, V> Map<String, Object> map(T source, Class<V> targetClass) {
		Map<String, Object> result = Maps.newHashMap();
		V vo = copy(source, targetClass);
		result = Converts.toMap(vo);
		format(result, targetClass);
		return result;
	}

	private static void format(Map<String, Object> result, Class<?> targetClass) {
		List<Field> fields = Reflections.getAllDeclaredField(targetClass, Object.class);
		for (Field field : fields) {
			Format format = field.getDeclaredAnnotation(Format.class);
			if (format != null) {
				Object value = result.get(field.getName());
				// 非空的情况下进行数据类型转换和格式化
				if (Emptys.isNotEmpty(value)) {
					switch (format.format()) {
					case MONEY:
						value = DecimalFormat.getCurrencyInstance(Locale.CHINA).format(Converts.toDoubleValue(value));
						break;
					case MONEY_CENT:
						value = DecimalFormat.getCurrencyInstance(Locale.CHINA).format(Converts.toDoubleValue(value) / 100);
						break;
					case PROPERTIES:
						value = loader.getMap(format.key()).get(Converts.toString(value));
						break;
					case ENUM:
						value = Converts.toString(value);
						break;
					case DATE:
							value = Dates.format(Converts.toDate(value), DatePattern.parttenOf(format.key()));
						break;
					default:
						break;
					}
				}
				result.put(field.getName() + "_fmt", Converts.toString(value));
			}
		}
	}

	/**
	 * 复制数据
	 * 
	 * @param source
	 * @param targetClass
	 * @return
	 */
	public static <T> T copy(Object source, Class<T> targetClass) {
		T target = null;
		try {
			target = copy(source, targetClass.newInstance());
			
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		}
		return target;
	}
	
	public static <T> T copy(Object source, T target) {
		try {
			Class<?> targetClass = target.getClass();
			Map<String, PropertyDescriptor> targetPDs = Arrays
					.asList(Introspector.getBeanInfo(targetClass , Object.class).getPropertyDescriptors())
					.stream().collect(Collectors.toMap(PropertyDescriptor::getName, p -> p));
			Map<String, PropertyDescriptor> sourcePDs = Arrays
					.asList(Introspector.getBeanInfo(source.getClass(), Object.class).getPropertyDescriptors()).stream()
					.collect(Collectors.toMap(PropertyDescriptor::getName, p -> p));
			for (Entry<String, PropertyDescriptor> entry : targetPDs.entrySet()) {
				PropertyDescriptor targetPD = entry.getValue();
				Method writeMethod = targetPD.getWriteMethod();
				if (writeMethod != null) {
					Field field = Reflections.getAccessibleField(target, targetPD.getName());
					if (field == null) {
						log.error("对象{}没有找到该属性：{}", target.getClass().getName(), targetPD.getName());
						continue;
					}
					//判断属性是否要去进行深入的获取值
					Transfer transfer = field.getDeclaredAnnotation(Transfer.class);
					if (transfer != null) {
						String mapping = transfer.mapping();
						Object value = null;
						if (Emptys.isNotEmpty(mapping)) {
							String[] mFields = StringUtils.split(mapping, ".");
							Object obj = source;
							for (String mField : mFields) {
								if (obj == null) {
									break;
								}
								String methodName = "get" + StringUtils.capitalize(mField);
								Method sourceMethod = Reflections.getAccessibleMethodByName(obj, methodName);
								if (sourceMethod == null) {
									throw new ProjectException(ErrorMessage.NOT_FOUND_METHOD.getMessage(methodName));
								}
								obj = sourceMethod.invoke(obj);
							}
							value = Converts.toByClass(obj, targetPD.getPropertyType());
							writeMethod.invoke(target, value);
						}
					} else {
						PropertyDescriptor sourcePD = sourcePDs.get(entry.getKey());
						if (sourcePD != null) {
							Method readMethod = sourcePD.getReadMethod();
							if (readMethod != null) {
								if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
									readMethod.setAccessible(true);
								}
								if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
									writeMethod.setAccessible(true);
								}
								// TODO 遇到集合类型的暂时先不处理
								if (!Collection.class.isAssignableFrom(sourcePD.getPropertyType())) {
									Object value = Converts.toByClass(readMethod.invoke(source), targetPD.getPropertyType());
									writeMethod.invoke(target, value);
								}
							}
						}
					}
				}
			}
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		}
		return target;
	}

	public static <T> T createRandomObject(Class<T> clazz, String... excludeFieldNames) {
		T obj = null;
		try {
			RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange('0', '9').build();
			obj = clazz.newInstance();
			BeanInfo beanInfo = Introspector.getBeanInfo(clazz, Identity.class);
			PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
			for (PropertyDescriptor descriptor : propertyDescriptors) {
				String fieldName = descriptor.getName();
				if ("class".equals(fieldName) || ArrayUtils.contains(excludeFieldNames, fieldName) || "id".equals(fieldName)) {
					continue;
				}
				if (BeanUtils.isSimpleValueType(descriptor.getPropertyType())) {
					Object value = null;
					if (Date.class.equals(descriptor.getPropertyType())) {
						value = Dates.now();
					} else if (descriptor.getPropertyType().isEnum()) {
						Object[] property = descriptor.getPropertyType().getEnumConstants();
						value = property[0];
					} else {
						value = Converts.toByClass(generator.generate(1), descriptor.getPropertyType());
					}
					Method writeMethod = descriptor.getWriteMethod();
					if (writeMethod != null) {
						log.info("填充属性{}", descriptor.getName());
						try {
							writeMethod.invoke(obj, value);
						} catch (Exception e) {
							log.info("填充属性{}失败", descriptor.getName());
						}
					}
				}
			}
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		}
		return obj;
	}
}
